﻿#include "JPieChart.h"

#include <QPainter>
#include <QPointF>
#include <QtMath>
#include <QDebug>

JPieChart::JPieChart(QWidget *parent)
    : QWidget{parent}
{
    initDefaultColorList();
    initAnimation();

    _useAnimation = true;
    _innerCircleVisible = true;
    _textLineVisible = true;
    _percentVisible = true;
}

void JPieChart::initDefaultColorList()
{
    _defaultColorList1 << QColor(7, 128, 207) << QColor(118, 80, 5) << QColor(250, 109, 29)
                       << QColor(14, 44, 130) << QColor(182, 181, 31) << QColor(218, 31, 24)
                       << QColor(112, 24, 102) << QColor(244, 122, 117) << QColor(0, 157, 178)
                       << QColor(2, 75, 81) << QColor(7, 128, 207) << QColor(118, 80, 5);

    _defaultColorList2 << QColor(99, 178, 238) << QColor(118, 218, 145) << QColor(248, 203, 127)
                       << QColor(248, 149, 136) << QColor(124, 214, 207) << QColor(145, 146, 171)
                       << QColor(120, 152, 225) << QColor(239, 166, 102) << QColor(237, 221, 134)
                       << QColor(153, 135, 206) << QColor(99, 178, 238) << QColor(118, 218, 145);
}

void JPieChart::initAnimation()
{
    _radian = 0;
    _pAnimation = new QPropertyAnimation(this, "_radian");
    _pAnimation->setDuration(750);
    _pAnimation->setStartValue(_radian);
    _pAnimation->setEndValue(TOTAL_RADIAN);
    _pAnimation->setEasingCurve(QEasingCurve::OutQuad);
}

void JPieChart::drawPieWithAnimation(QPainter *painter)
{
    // 设置画笔
    QPen pen(Qt::transparent);
    pen.setWidth(2);
    pen.setStyle(Qt::SolidLine);
    painter->setPen(pen);

    // 设置画刷
    QBrush brush(Qt::red);
    brush.setStyle(Qt::SolidPattern);
    painter->setBrush(brush);

    // 负值表示顺时针方向
    int nTotalRadian = 0;
    int nTempRadian;
    QColor tempColor;
    QString tempText;
    int nPointNum;

    int outLen = qMin(this->width() - PIE_OUT_MARGIN, this->height() - PIE_OUT_MARGIN);
    int radius = outLen / 2;
    QPoint outStartPos((this->width() - outLen) / 2, (this->height() - outLen) / 2);
    QRect outRect(outStartPos, QSize(outLen, outLen));

    for (int i = 0; i < _radianList.count(); i++)
    {
        tempColor = _defaultColorList2.at(i);

        painter->setBrush(tempColor);

        nTempRadian = _radianList.at(i);

        if (nTotalRadian + nTempRadian >= _radian)
        {
            painter->drawPie(outRect, -1 * nTotalRadian, -1 * (_radian - nTotalRadian));

            if (i == _radianList.count() - 1)
            {
                nTotalRadian += nTempRadian;
                nPointNum = (_valueList.at(i) - int(_valueList.at(i)) < 0.1) ? 0 : 1; // 判断 _valueList.at(i) 的第一位小数是否是0
                tempText = QString::number(_valueList.at(i), 'f', nPointNum);
                if (_percentVisible)
                    tempText = tempText + "(" + QString::number(_percentList.at(i) * 100, 'f', 1) + "%)";

                if (_textLineVisible)
                    drawValueTextWithLine(painter, tempColor, nTotalRadian - (nTempRadian / 2), radius, tempText);
                else
                    drawValueTextWithoutLine(painter, tempColor, nTotalRadian - (nTempRadian / 2), radius, tempText);
            }
            break;
        }
        else
            painter->drawPie(outRect, -1 * nTotalRadian, -1 * nTempRadian);

        nTotalRadian += nTempRadian;
        nPointNum = (_valueList.at(i) - int(_valueList.at(i)) < 0.1) ? 0 : 1; // 判断 _valueList.at(i) 的第一位小数是否是0
        tempText = QString::number(_valueList.at(i), 'f', nPointNum);
        if (_percentVisible)
            tempText = tempText + "(" + QString::number(_percentList.at(i) * 100, 'f', 1) + "%)";

        if (_textLineVisible)
            drawValueTextWithLine(painter, tempColor, nTotalRadian - (nTempRadian / 2), radius, tempText);
        else
            drawValueTextWithoutLine(painter, tempColor, nTotalRadian - (nTempRadian / 2), radius, tempText);
    }

    if (_innerCircleVisible)
    {
        int inLen = outLen / 2;
        QPoint inStartPos((this->width() - inLen) / 2, (this->height() - inLen) / 2);
        QRect inRect(inStartPos, QSize(inLen, inLen));
        painter->setBrush(QBrush(Qt::white));
        painter->drawPie(inRect, 0, TOTAL_RADIAN);
    }
}

void JPieChart::drawPieWithoutAnimation(QPainter *painter)
{
    // 设置画笔
    QPen pen(Qt::transparent);
    pen.setWidth(2);
    pen.setStyle(Qt::SolidLine);
    painter->setPen(pen);

    // 设置画刷
    QBrush brush(Qt::red);
    brush.setStyle(Qt::SolidPattern);
    painter->setBrush(brush);

    // 负值表示顺时针方向
    int nTotalRadian = 0;
    int nTempRadian;
    QColor tempColor;
    QString tempText;
    int nPointNum;

    int outLen = qMin(this->width(), this->height());
    int radius = outLen / 2;
    QPoint outStartPos((this->width() - outLen) / 2, (this->height() - outLen) / 2);
    QRect outRect(outStartPos, QSize(outLen, outLen));

    for (int i = 0; i < _radianList.count(); i++)
    {
        tempColor = _defaultColorList2.at(i);

        painter->setBrush(tempColor);

        nTempRadian = _radianList.at(i);
        painter->drawPie(outRect, -1 * nTotalRadian, -1 * nTempRadian);

        nTotalRadian += nTempRadian;
        nPointNum = (_valueList.at(i) - int(_valueList.at(i)) < 0.1) ? 0 : 1; // 判断 _valueList.at(i) 的第一位小数是否是0
        tempText = QString::number(_valueList.at(i), 'f', nPointNum);
        if (_percentVisible)
            tempText = tempText + "(" + QString::number(_percentList.at(i) * 100, 'f', 1) + "%)";

        if (_textLineVisible)
            drawValueTextWithLine(painter, tempColor, nTotalRadian - (nTempRadian / 2), radius, tempText);
        else
            drawValueTextWithoutLine(painter, tempColor, nTotalRadian - (nTempRadian / 2), radius, tempText);
    }

    if (_innerCircleVisible)
    {
        int inLen = outLen / 2;
        QPoint inStartPos((this->width() - inLen) / 2, (this->height() - inLen) / 2);
        QRect inRect(inStartPos, QSize(inLen, inLen));
        painter->setBrush(QBrush(Qt::white));
        painter->drawPie(inRect, 0, TOTAL_RADIAN);
    }
}

void JPieChart::drawValueTextWithLine(QPainter *painter, QColor color, int radian, int radius, QString text)
{
    painter->save();

    painter->translate(this->width() / 2, this->height() / 2); //将坐标原点平移到到窗口正中心

    int nExtendLen = 0.1 * radius;
    int nRadius = radius + nExtendLen;

    QPen pen(color);
    pen.setWidth(2);
    pen.setStyle(Qt::SolidLine);
    painter->setPen(pen);

    painter->setFont(QFont("Arial", nRadius / FONTSIZE_FACTOR, QFont::Bold));

    QFontMetricsF fm = QFontMetricsF(painter->font());

    int angle = radian / 16;
    float fAngleArc = (angle % 360) * M_PI / 180;

    //此时就相当于一个数学题，已知圆的圆心(x0, y0)、半径r和以及点A(x1, y1)与X轴的夹角a，求该点A的坐标
    //x1 = x0 + r * cos(a * π / 180)
    //y1 = y0 + r * sin(a * π / 180)
    //并且圆心就是(0, 0)
    int x = nRadius * cos(fAngleArc);
    int y = nRadius * sin(fAngleArc);

    painter->drawLine(QPointF(0, 0), QPointF(x, y));

    int textWidth = (int)fm.horizontalAdvance(text);
    int textHeight = (int)fm.height();

    QPointF lineEndPoint, textStartPoint;
    if (angle <= 90 || angle >= 270)
    {
        lineEndPoint = QPointF(x + nExtendLen, y);
        textStartPoint = QPointF(x + nExtendLen + 10, y + textHeight / 4);
    }
    else
    {
        lineEndPoint = QPointF(x - nExtendLen, y);
        textStartPoint = QPointF(x - nExtendLen - textWidth - 10, y + textHeight / 4);
    }

    painter->drawLine(QPointF(x, y), lineEndPoint);

    painter->drawText(textStartPoint, text);

    painter->restore();
}

void JPieChart::drawValueTextWithoutLine(QPainter *painter, QColor color, int radian, int radius, QString text)
{
    painter->save();

    painter->translate(this->width() / 2, this->height() / 2); //将坐标原点平移到到窗口正中心

    int nExtendLen = 0.1 * radius;
    int nRadius = radius + nExtendLen;

    QPen pen(color);
    pen.setWidth(2);
    pen.setStyle(Qt::SolidLine);
    painter->setPen(pen);

    painter->setFont(QFont("Arial", nRadius / FONTSIZE_FACTOR, QFont::Bold));

    QFontMetricsF fm = QFontMetricsF(painter->font());

    int angle = radian / 16;
    float fAngleArc = (angle % 360) * M_PI / 180;

    //此时就相当于一个数学题，已知圆的圆心(x0, y0)、半径r和以及点A(x1, y1)与X轴的夹角a，求该点A的坐标
    //x1 = x0 + r * cos(a * π / 180)
    //y1 = y0 + r * sin(a * π / 180)
    //并且圆心就是(0, 0)
    int x = nRadius * cos(fAngleArc);
    int y = nRadius * sin(fAngleArc);

    int textWidth = (int)fm.horizontalAdvance(text);
    int textHeight = (int)fm.height();
    x = x - textWidth / 2;
    y = y + textHeight / 4;

    painter->drawText(QPointF(x, y), text);

    painter->restore();
}

void JPieChart::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);

    // 设置抗拒齿，设置抗拒齿后，图形的边界更圆滑，但绘图效率会降低
    painter.setRenderHint(QPainter::Antialiasing);

    if (_useAnimation)
        drawPieWithAnimation(&painter);
    else
        drawPieWithoutAnimation(&painter);
}

void JPieChart::_setValue(int radian)
{
    _radian = radian;
    repaint();
}

int JPieChart::_getValue()
{
    return _radian;
}

void JPieChart::setAnimation(bool useAnimation)
{
    _useAnimation = useAnimation;
}

void JPieChart::setInnerCircleVisible(bool visible)
{
    _innerCircleVisible = visible;
}

void JPieChart::setTextLineVisible(bool visible)
{
    _textLineVisible = visible;
}

void JPieChart::setPercentVisible(bool visible)
{
    _percentVisible = visible;
}

void JPieChart::setValue(QList<double> valueList)
{
    _valueList = valueList;
    qSort(_valueList.begin(), _valueList.end());
    _radianList.clear();
    _percentList.clear();

    double totalValue = 0.0;
    for (int i = 0; i < _valueList.count(); i++)
        totalValue += _valueList.at(i);

    double tempValue;
    double tempPercent;
    double leftPercent = 1.0;
    int leftRadian = TOTAL_RADIAN;
    int tempAngle;
    for (int i = 0; i < _valueList.count(); i++)
    {
        if (i == _valueList.count() - 1)
        {
            _radianList.append(leftRadian);
            _percentList.append(leftPercent);
        }
        else
        {
            tempValue = _valueList.at(i);
            tempPercent = tempValue / totalValue;
            _percentList.append(tempPercent);
            leftPercent -= tempPercent;

            if (qFuzzyCompare(tempValue, 0.0))
                continue;

            tempAngle = tempPercent * TOTAL_RADIAN;
            if (tempAngle == 0)
                tempAngle = 1;

            _radianList.append(tempAngle);
            leftRadian -= tempAngle;
        }
    }

    _pAnimation->start();
}
